package com.mrx.commons.task;

import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.ImportOrderer;
import com.google.googlejavaformat.java.JavaFormatterOptions;
import com.mrx.commons.task.exception.TaskExecuteException;
import com.mrx.commons.task.logger.ITaskLogger;
import com.mrx.commons.task.logger.TaskLogger;
import com.mrx.commons.task.model.TaskCreateVO;
import com.mrx.commons.task.model.XTask;
import lombok.SneakyThrows;
import org.codehaus.janino.SimpleCompiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Mr.X
 * @since 2023-12-08 12:31
 **/
public class SchedulerTask {

    private static final Logger logger = LoggerFactory.getLogger(SchedulerTask.class);
    private static final String CLASS_NAME = "Main";
    private static final String MAIN_METHOD_NAME = "main";
    private static final Formatter formatter = new Formatter(JavaFormatterOptions.builder().formatJavadoc(true).build());
    private static final Map<String, XTask> SOURCE_MAP = new ConcurrentHashMap<>();

    public static XTask createTask(TaskCreateVO createVO) {
        logger.info("creating task: \n{}", createVO.toJSONString());
        XTask task = SOURCE_MAP.compute(createVO.getName(), (a, b) -> {
            if (b != null) {
                throw new TaskExecuteException("任务 " + createVO.getName() + " 已存在");
            }
            return cook(createVO);
        });
        logger.info("task created");
        return task;
    }

    public static String runTask(String taskName) {
        Thread currentThread = Thread.currentThread();
        String[] split = currentThread.getName().split("-");
        currentThread.setName("x-task-" + taskName + "-" + split[split.length - 1]);
        logger.debug("preparing to run task: {}", taskName);
        XTask task = SOURCE_MAP.get(taskName);
        if (task == null) {
            throw new TaskExecuteException("任务不存在: " + taskName);
        }
        try {
            run(task);
            return getLogFromTask(task, true);
        } catch (Exception e) {
            task.collectStackTrace(e);
            return getLogFromTask(task, false);
        }
    }

    private static void run(XTask task) {
        try {
            task.getMainMethod().invoke(null, (Object) new String[]{});
        } catch (Exception e) {
            Throwable throwable = e;
            if (throwable instanceof InvocationTargetException) {
                throwable = ((InvocationTargetException) throwable).getTargetException();
            }
            throw new TaskExecuteException(throwable);
        }
    }

    @SneakyThrows
    private static XTask cook(TaskCreateVO createVO) {
        String source = createVO.getSource();
        if (source.isBlank()) {
            throw new TaskExecuteException("任务源码不能为空");
        }
        source = importPackage(TaskLogger.class, Logger.class) + source;
        source = ImportOrderer.reorderImports(formatter.formatSource(source), JavaFormatterOptions.Style.GOOGLE);
        createVO.setSource(source);
        logger.debug("cooking: \n{}", source);
        SimpleCompiler compiler = new SimpleCompiler();
        compiler.setDebuggingInformation(true, true, true);
        compiler.setParentClassLoader(Thread.currentThread().getContextClassLoader());
        try {
            compiler.cook(source);
            Class<?> clazz = compiler.getClassLoader().loadClass(CLASS_NAME);
            Method mainMethod = clazz.getMethod(MAIN_METHOD_NAME, String[].class);
            ITaskLogger taskLogger = null;
            for (Field field : clazz.getDeclaredFields()) {
                // 只处理静态 logger
                if (Modifier.isStatic(field.getModifiers())) {
                    field.setAccessible(true);
                    Object obj = field.get(null);
                    if (obj instanceof ITaskLogger) {
                        taskLogger = (ITaskLogger) obj;
                        break;
                    }
                }
            }
            return XTask.of(createVO, mainMethod, taskLogger);
        } catch (NoSuchMethodException e) {
            throw new TaskExecuteException("该类没有 main 方法");
        } catch (Exception e) {
            throw new TaskExecuteException(e);
        }
    }

    private static String importPackage(Class<?>... classes) {
        StringBuilder sb = new StringBuilder();
        for (Class<?> clazz : classes) {
            sb.append(String.format("import %s.%s;", clazz.getPackageName(), clazz.getSimpleName()));
        }
        return sb.toString();
    }

    private static String getLogFromTask(XTask task, boolean success) {
        String log;
        if (success) {
            log = "任务执行成功, 日志: \n" + task.getLog();
        } else {
            log = "任务执行失败, 源码: \n" + task.getVo().getSource() + "\n\n日志: \n" + task.getLog();
        }
        return log;
    }

    public static void main(String[] args) {
        String source = "import java.util.function.Supplier;" +
                "public class Main {" +
                "private static final Logger logger = TaskLogger.getLogger(Main.class);" +
                "public static void main(String[] args) throws Exception {\n" +
                "System.out.println(\"HelloWorld\");" +
                "System.out.println(logger);" +
                "logger.debug(\"test: {}\", 1);" +
                "System.out.println(((Supplier)logger).get());" +
                "System.out.println(((Supplier)null).get());" +
                "    }" +
                "}";
        String taskName = "test-task";
        createTask(TaskCreateVO.of(taskName, source));
        System.out.println(runTask(taskName));
    }

}
